package com.final_project.zombiemenace.screens;

import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;

import android.content.Context;
import android.content.Intent;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Typeface;
import android.media.AudioManager;

import com.final_project.zombiemenace.Animation;
import com.final_project.zombiemenace.PlayTheGame;
import com.final_project.zombiemenace.model.Assets;
import com.final_project.zombiemenace.model.BossZombie;
import com.final_project.zombiemenace.model.Bullet;
import com.final_project.zombiemenace.model.Circle;
import com.final_project.zombiemenace.model.Joystick;
import com.final_project.zombiemenace.model.Player;
import com.final_project.zombiemenace.model.TempImage;
import com.final_project.zombiemenace.model.Zombie;
import com.final_project.zombiemenace.settings.Settings;
import com.final_project.zombiemenace.ui.GameState;
import com.kilobolt.framework.Game;
import com.kilobolt.framework.Graphics;
import com.kilobolt.framework.Image;
import com.kilobolt.framework.Input.TouchEvent;
import com.kilobolt.framework.Screen;

public class GameScreenZombie extends Screen {
	
	private static final int ZOMBIE_HEALTH_BAR_HEIGHT = 5;
	private static final int ZOMBIE_HEALTH_BAR_FIX_Y = 25;
	private static final int ZOMBIE_HEALTH_BAR_FIX_X = 20;
	private static final float INITIAL_ZOMBIE_SPEED = 0.2f;
	private static final int DEFAULT_TEXT_SIZE = 30;
	private static final int FRAME_DURATION = 500;
	private static final float ZOMBIE_SPEED_INCREASE_COEFICIENT = 0.05f;
	private static final int ZOMBIE_DAMAGE = 5;
	private static final int ZOMBIE_INITIAL_HEALTH = 100;
	private static final int PLAYER_INITIAL_HEALTH = 100;
	private static final int POINTS_PER_ZOMBIE_KILL = 100;
	private static final int ZOMBIE_HORDE_COUNT = 15;

	private GameState state = GameState.Ready;
	private static Player player;
	public static List<Zombie> zombieHorde;
	private List<TempImage> temps;
	private TouchEvent lastEvent;
	private Image joystick, joystick_bg, background, character, bullet,
			greenZombie1, greenZombie2, greenZombie3, greenZombie4,
			purpleZombie1, purpleZombie2, purpleZombie3, purpleZombie4,
			bloodStain, pauseButton;
	private Animation greenZombieAnim, purpleZombieAnim;
	private Paint paint;
	private Joystick leftJoystick, rightJoystick;
	public static float angleRotation;
	private List<Bullet> firedBullets;
	private Settings settings;
	private int deadZombieInWave;
	private boolean shooting;

	public GameScreenZombie(Game game) {
		super(game);
		deadZombieInWave = 0;
		settings = new Settings(game.getContext());
		player = new Player(PLAYER_INITIAL_HEALTH,
				(game.getGraphics().getWidth()) / 2,
				(game.getGraphics().getHeight()) / 2);
		// top,right,left,bottom,initx,inity
		leftJoystick = new Joystick(408, 75, 443, 35, 55, 425);
		rightJoystick = new Joystick(408, 765, 443, 725, 745, 425);
		zombieHorde = new CopyOnWriteArrayList<Zombie>();
		firedBullets = new CopyOnWriteArrayList<Bullet>();
		temps = new CopyOnWriteArrayList<TempImage>();
		initImages();
		initAnimations();
		initPaint(game);
		initializeZombieHorde();
		setVolume(game);
	}

	private void initPaint(Game game) {
		paint = new Paint();
		paint.setTextSize(DEFAULT_TEXT_SIZE);
		paint.setTextAlign(Paint.Align.CENTER);
		paint.setAntiAlias(true);
		paint.setTypeface(Typeface.createFromAsset(game.getContext()
				.getAssets(), "bloody.ttf"));
		paint.setColor(Color.rgb(209, 12, 12));
	}

	private void initAnimations() {
		greenZombieAnim = new Animation();
		greenZombieAnim.addFrame(greenZombie1, FRAME_DURATION);
		greenZombieAnim.addFrame(greenZombie2, FRAME_DURATION);
		greenZombieAnim.addFrame(greenZombie3, FRAME_DURATION);
		greenZombieAnim.addFrame(greenZombie4, FRAME_DURATION);
		purpleZombieAnim = new Animation();
		purpleZombieAnim.addFrame(purpleZombie1, FRAME_DURATION);
		purpleZombieAnim.addFrame(purpleZombie2, FRAME_DURATION);
		purpleZombieAnim.addFrame(purpleZombie3, FRAME_DURATION);
		purpleZombieAnim.addFrame(purpleZombie4, FRAME_DURATION);
	}

	private void initImages() {
		joystick_bg = Assets.joystickBackground;
		joystick = Assets.joystick;
		background = Assets.background;
		character = Assets.character;
		bullet = Assets.bullet;
		greenZombie1 = Assets.greenZombie1;
		greenZombie2 = Assets.greenZombie2;
		greenZombie3 = Assets.greenZombie3;
		greenZombie4 = Assets.greenZombie4;
		purpleZombie1 = Assets.purpleZombie1;
		purpleZombie2 = Assets.purpleZombie2;
		purpleZombie3 = Assets.purpleZombie3;
		purpleZombie4 = Assets.purpleZombie4;
		bloodStain = Assets.bloodStain;
		bullet = Assets.bullet;
		pauseButton = Assets.pauseButton;
	}

	private void setVolume(Game game) {
		AudioManager audioManager = (AudioManager) game.getContext()
				.getSystemService(Context.AUDIO_SERVICE);
		audioManager.setStreamVolume(AudioManager.STREAM_MUSIC,
				(int) settings.getVolumeFromSharedPrefs(), 0);
	}

	private void initializeZombieHorde() {
		for (int zombieIndex = 0; zombieIndex < ZOMBIE_HORDE_COUNT; zombieIndex++) {
			if (zombieIndex < ZOMBIE_HORDE_COUNT - 1) {
				zombieHorde.add(new Zombie(game, ZOMBIE_INITIAL_HEALTH,
						ZOMBIE_DAMAGE, INITIAL_ZOMBIE_SPEED));
			} else {
				zombieHorde.add(new BossZombie(game, ZOMBIE_INITIAL_HEALTH,
						ZOMBIE_DAMAGE, INITIAL_ZOMBIE_SPEED));
			}
		}
	}

	/**
	 * Update method that defines what is the current state of the game.
	 */
	@Override
	public void update(float deltaTime) {
		List<TouchEvent> touchEvents = game.getInput().getTouchEvents();
		switch (state) {
		case Ready:
			updateReady(touchEvents);
			return;
		case Running:
			updateRunning(touchEvents, deltaTime);
			return;
		case Paused:
			updatePaused(touchEvents);
			return;
		case GameOver:
			updateGameOver(touchEvents);
			return;
		default:
			return;
		}
	}

	private void updateReady(List<TouchEvent> touchEvents) {
		if (touchEvents.size() > 0)
			state = GameState.Running;
		Assets.gameplayMusics.play();
		Assets.gameplayMusics.setLooping(true);
		Assets.gameplayMusics.setVolume(100.0f);
	}

	private void updateRunning(List<TouchEvent> touchEvents, float deltaTime) {
		if (isGamePaused(touchEvents)) {
			pause();
		} else {
			resume();
		}
		// update input
		updateJoysticks(touchEvents);
		// check if player is dead
		checkDeathCondition();
		// update all
		updateAllZombies();
		updateZombiesInCollision();
		player.update();
		if (shooting) {
			fireBullet();
		}
		updateBullets();
		animate();
	}

	private boolean isGamePaused(List<TouchEvent> touchEvents) {
		for (TouchEvent touchEvent : touchEvents) {
			if (touchEvent.type == TouchEvent.TOUCH_DOWN
					&& (inBounds(touchEvent, 10, 10, pauseButton.getWidth(),
							pauseButton.getWidth()))) {
				return true;
			}
		}
		return false;
	}

	private void updatePaused(List<TouchEvent> touchEvents) {
		for (TouchEvent touchEvent : touchEvents) {
			if (touchEvent.type == TouchEvent.TOUCH_DOWN) {
				if (inBounds(touchEvent, 280, 295, 240, 50)) {
					backButton();
				} else if (inBounds(touchEvent, 320, 215, 160, 50)) {
					resume();
				}
			}
		}
	}

	private void updateGameOver(List<TouchEvent> touchEvents) {
		stopShootingSound();
		for (TouchEvent touchEvent : touchEvents) {
			if (touchEvent.type == TouchEvent.TOUCH_DOWN) {
				if (inBounds(touchEvent, 280, 295, 240, 50)) {
					backButton();
				} else if (inBounds(touchEvent, 320, 215, 160, 50)) {
					nullify();
					Intent restartGame = new Intent(game.getContext(),
							PlayTheGame.class);
					game.getContext().startActivity(restartGame);
					android.os.Process.killProcess(android.os.Process.myPid());
					return;
				}
			}
		}

	}

	private void checkDeathCondition() {
		if (player.isPlayerDead(game)) {
			state = GameState.GameOver;
		}
	}

	private void updateBullets() {
		for (Bullet bullet : firedBullets) {
			if (bullet.isVisible()) {
				bullet.update();
			} else {
				firedBullets.remove(bullet);
			}
		}
	}

	// move all zombies
	private void updateAllZombies() {
		for (Zombie zombie : zombieHorde) {
			if (zombie.isAlive()) {
				zombie.update();
			} else {
				Assets.zombieInPain.play(100.0f);
				zombieHorde.remove(zombie);
				deadZombieInWave++;
				temps.add(new TempImage(temps, game, zombie.getCenterX(),
						zombie.getCenterY(), bloodStain));
				player.setPoints(player.getPoints() + POINTS_PER_ZOMBIE_KILL);
				// when Zombie dies create a new one and increase zombie
				// speed with coef
				if (deadZombieInWave >= ZOMBIE_HORDE_COUNT) {
					deadZombieInWave = 0;
					zombieHorde.add(new BossZombie(game, ZOMBIE_INITIAL_HEALTH,
							ZOMBIE_DAMAGE, zombie.getZombieSpeed()
									+ ZOMBIE_SPEED_INCREASE_COEFICIENT));
				} else {
					zombieHorde.add(new Zombie(game, ZOMBIE_INITIAL_HEALTH,
							ZOMBIE_DAMAGE, zombie.getZombieSpeed()
									+ ZOMBIE_SPEED_INCREASE_COEFICIENT));
				}
			}
		}
	}

	/**
	 * Zombie to zombie collision logic
	 */
	private void updateZombiesInCollision() {
		for (int zombieIndex = 0; zombieIndex < zombieHorde.size(); zombieIndex++) {
			Zombie zombie = zombieHorde.get(zombieIndex);
			for (int otherZombieIndex = zombieIndex + 1; otherZombieIndex < zombieHorde
					.size(); otherZombieIndex++) {
				Zombie otherZombie = zombieHorde.get(otherZombieIndex);
				if (zombie.checkZombieToZombieCollision(otherZombie)) {
					if (calculateDistanceBetweenZombieAndPlayer(zombie) <= calculateDistanceBetweenZombieAndPlayer(otherZombie)) {
						stopZombie(otherZombie);
					} else {
						stopZombie(zombie);
					}
				}
			}
		}
	}

	private float calculateDistanceBetweenZombieAndPlayer(Zombie zombie) {
		return Circle.calculateDistanceBetweenPoints(
				zombie.zombieCircle.getCenterX(),
				zombie.zombieCircle.getCenterY(),
				player.playerCircle.getCenterX(),
				player.playerCircle.getCenterY());
	}

	private void stopZombie(Zombie zombie) {
		zombie.setCenterX(zombie.getCenterX() - zombie.getSpeedX());
		zombie.setCenterY(zombie.getCenterY() - zombie.getSpeedY());
	}

	private void updateJoysticks(List<TouchEvent> touchEvents) {
		int length = touchEvents.size();
		for (int eventIndex = 0; eventIndex < length; eventIndex++) {
			TouchEvent event = touchEvents.get(eventIndex);
			if (event == null && lastEvent == null) {
				return;
			} else if (event == null && lastEvent != null) {
				event = lastEvent;
			} else {
				lastEvent = event;
			}
			if (inBounds(event, 0, 380, 100, 470)) {
				updateLeftJoystick(event);
			} else if (inBounds(event, 700, 380, 790, 470)) {
				updateRightJoystick(event);
			} else {
				resetJoysticks();
			}
		}
	}

	private void resetJoysticks() {
		leftJoystick.resetJoystick();
		rightJoystick.resetJoystick();
		player.resetPlayerSpeed();
		shooting = false;
		stopShootingSound();
	}

	private void updateRightJoystick(TouchEvent event) {
		rightJoystick.updateJoystick(event);
		if (event.type != TouchEvent.TOUCH_UP) {
			shooting = true;
		} else {
			shooting = false;
			stopShootingSound();
		}
	}

	private void fireBullet() {
		angleRotation = (float) Math.atan2(
				(rightJoystick.getCenterPointY() - rightJoystick.getInitY()),
				(rightJoystick.getCenterPointX() - rightJoystick.getInitX()));
		Bullet newBullet = player.attack(angleRotation);
		firedBullets.add(newBullet);
		if (!Assets.shoot.isPlaying()) {
			Assets.shoot.play();
			Assets.shoot.setLooping(true);
		}
	}

	private void updateLeftJoystick(TouchEvent event) {
		leftJoystick.updateJoystick(event);
		player.updateSpeed(leftJoystick.getCenterPointX(),
				leftJoystick.getInitX(), leftJoystick.getCenterPointY(),
				leftJoystick.getInitY());
	}

	private void stopShootingSound() {
		if (Assets.shoot.isPlaying()) {
			Assets.shoot.stop();
		}
	}

	// check if specific touch is in some bounds
	private boolean inBounds(TouchEvent event, int x, int y, int width,
			int height) {
		if (event.x > x && event.x < x + width - 1 && event.y > y
				&& event.y < y + height - 1)
			return true;
		else
			return false;
	}

	/**
	 * Method for drawing the game interface after each update
	 */
	@Override
	public void paint(float deltaTime) {
		drawGamePlay();
		if (state == GameState.Ready)
			drawReadyUI();
		if (state == GameState.Running)
			drawRunningUI();
		if (state == GameState.Paused)
			drawPausedUI();
		if (state == GameState.GameOver)
			drawGameOverUI();
	}

	private void drawGamePlay() {
		Graphics g = game.getGraphics();
		// Background must be drawn first!
		g.drawImage(background, 0, 0);
		// Draw bullets
		drawBullets(g);
		// Draw blood stains for dead zombies
		drawBloodStains(g);
		// Draw player
		drawPlayer(g);
		// Draw zombies
		drawZombies(g);
	}

	private void drawPlayerHealthBar(Graphics g) {
		int playersHealth = player.getHealth();
		g.drawRectWithBorder(10, 60, playersHealth, 20, Color.RED);
	}

	private void drawZombies(final Graphics g) {
		for (int i = 0; i < zombieHorde.size(); i++) {
			Zombie zombieToDraw = zombieHorde.get(i);
			if (!zombieToDraw.isVisible()) {
				continue;
			}
			if (zombieToDraw instanceof BossZombie) {
				drawZombie(g, zombieToDraw, purpleZombieAnim);
			} else {
				drawZombie(g, zombieToDraw, greenZombieAnim);
			}
			drawZombieHealthBar(g, zombieToDraw);
		}
	}

	private void drawZombie(final Graphics g, Zombie zombieToDraw,
			Animation animation) {
		float zombieScaledX = zombieToDraw.getCenterX()
				- (animation.getImage().getWidth() / 2);
		float zombieScaledY = zombieToDraw.getCenterY()
				- (animation.getImage().getHeight() / 2);
		Matrix rotatedZombieMatrix = zombieToDraw.rotateZombie(zombieToDraw,
				zombieScaledX, zombieScaledY);
		g.rotateImage(animation.getImage(), rotatedZombieMatrix, zombieScaledX,
				zombieScaledY);
//		g.drawCircle(zombieToDraw.zombieCircle.getCenterX(),
//				zombieToDraw.zombieCircle.getCenterY(),
//				zombieToDraw.zombieCircle.getRadius(), paint);
	}

	private void drawZombieHealthBar(final Graphics g, Zombie zombieToDraw) {
		g.drawRectWithBorder((int) zombieToDraw.getCenterX()
				- ZOMBIE_HEALTH_BAR_FIX_X, (int) zombieToDraw.getCenterY()
				- ZOMBIE_HEALTH_BAR_FIX_Y, (zombieToDraw.getHealth() / 2),
				ZOMBIE_HEALTH_BAR_HEIGHT, Color.GREEN);
	}

	private void drawPlayer(final Graphics g) {
		float playerScaledX = player.getCenterX() - (character.getWidth() / 2);
		float playerScaledY = player.getCenterY() - (character.getHeight() / 2);
		g.rotateImage(character, player.updateRotation(), playerScaledX,
				playerScaledY);
//		paint.setColor(Color.BLACK);
//		g.drawCircle(player.playerCircle.getCenterX(),
//				player.playerCircle.getCenterY(),
//				player.playerCircle.getRadius(), paint);
//		paint.setColor(Color.GREEN);
	}

	private void drawBloodStains(final Graphics g) {
		for (TempImage temp : temps) {
			g.drawImage(temp.getImg(), temp.getX(), temp.getY());
			temp.update();
		}
	}

	private void drawBullets(final Graphics g) {
		for (Bullet firedBullet : firedBullets) {
			g.drawImage(bullet, firedBullet.getCoordX(),
					firedBullet.getCoordY());
		}
	}

	private void drawGameOverUI() {
		Graphics g = game.getGraphics();
		g.drawARGB(155, 0, 0, 0);
		paint.setTextSize(45);
		paint.setColor(Color.RED);
		g.drawString("GaMe oVeR.", 400, 140, paint);
		paint.setTextSize(30);
		paint.setColor(Color.GREEN);
		g.drawString("Restart", 400, 250, paint);
		g.drawString("Back To Menu", 400, 330, paint);
	}

	private void drawPausedUI() {
		Graphics g = game.getGraphics();
		g.drawARGB(155, 0, 0, 0);
		paint.setTextSize(45);
		paint.setColor(Color.RED);
		g.drawString("Paused.", 400, 140, paint);
		paint.setTextSize(30);
		paint.setColor(Color.GREEN);
		g.drawString("Resume", 400, 250, paint);
		g.drawString("Back To Menu", 400, 330, paint);
	}

	private void drawReadyUI() {
		Graphics g = game.getGraphics();
		g.drawARGB(155, 0, 0, 0);
		g.drawString("Tap to Start.", 400, 240, paint);

	}

	private void drawRunningUI() {
		Graphics g = game.getGraphics();
		// joystick half width and height;
		int joystickHalfWidth = joystick.getWidth() / 2;
		int joystickHalfHeight = joystick.getHeight() / 2;
		drawLeftJoystick(g, joystickHalfWidth, joystickHalfHeight);
		drawRightJoystick(g, joystickHalfWidth, joystickHalfHeight);
		paint.setColor(Color.rgb(50, 248, 50));
		g.drawString("Score " + String.valueOf(player.getPoints()), 650, 30,
				paint);
		drawPlayerHealthBar(g);
		drawPauseButton(g);
	}

	private void drawPauseButton(Graphics g) {
		int pauseButtonY = 10;
		int pauseButtonX = 10;
		g.drawImage(pauseButton, pauseButtonX, pauseButtonY);
	}

	private void drawRightJoystick(Graphics g, int joystickHalfWidth,
			int joystickHalfHeight) {
		// scale second joystick coords
		int rightJoystickXScaled = rightJoystick.getCenterPointX()
				- joystickHalfWidth;
		int rightJoystickYScaled = rightJoystick.getCenterPointY()
				- joystickHalfHeight;
		// draw right joystick
		g.drawImage(joystick_bg, 700, 380);
		g.drawImage(joystick, rightJoystickXScaled, rightJoystickYScaled);
	}

	private void drawLeftJoystick(Graphics g, int joystickHalfWidth,
			int joystickHalfHeight) {
		// scale left joystick coords
		int leftJoystickXScaled = leftJoystick.getCenterPointX()
				- joystickHalfWidth;
		int leftJoystickYScaled = leftJoystick.getCenterPointY()
				- joystickHalfHeight;
		// draw left joystick
		g.drawImage(joystick_bg, 10, 380);
		g.drawImage(joystick, leftJoystickXScaled, leftJoystickYScaled);
	}

	public void animate() {
		greenZombieAnim.update(100);
		purpleZombieAnim.update(50);
	}

	/*
	 * Method that is called when game is over to clean everything
	 */
	private void nullify() {
		paint = null;
		player = null;
		zombieHorde = null;
		character = null;
		greenZombie1 = null;
		greenZombie2 = null;
		greenZombie3 = null;
		greenZombie4 = null;
		purpleZombie1 = null;
		purpleZombie2 = null;
		purpleZombie3 = null;
		purpleZombie4 = null;
		bloodStain = null;
		firedBullets = null;
		greenZombieAnim = null;
		purpleZombieAnim = null;
		temps = null;
		leftJoystick = null;
		rightJoystick = null;
		System.gc();
	}

	@Override
	public void pause() {
		if (state == GameState.Running)
			state = GameState.Paused;
	}

	@Override
	public void resume() {
		if (state == GameState.Paused)
			state = GameState.Running;
	}

	@Override
	public void dispose() {
	}

	@Override
	public void backButton() {
		android.os.Process.killProcess(android.os.Process.myPid());
	}

	public static Player getPlayer() {
		return player;
	}
}